package kGeom;

import java.util.EmptyStackException;

public class kVec extends kElement {
	public float	x,y,z;
	
	/*
	 * CONSTRUCTORS
	 * 
	 * 
	 * 
	 */

	/**
	 * Constructs and initializes a kVec to (0,0,0).
	 */
	public kVec() {
		this(0, 0, 0);
	}

	/**
	 * REFERENCE CONSTRUCTOR Constructs and initializes a kVec from the
	 * specified xyz coordinates.
	 * 
	 * @param xx
	 * @param yy
	 * @param zz
	 */
	public kVec(float xx, float yy, float zz) {
		x = xx;
		y = yy;
		z = zz;
	}

	/**
	 * Constructs and initializes a kVec from the specified xy coordinates at z = 0.
	 * 
	 * @param x
	 * @param y
	 */
	public kVec(float x, float y) {
		this(x, y, 0);
	}

	/**
	 * Constructs and initializes a kVec from the array of length 3.
	 * 
	 * @param coords
	 */
	public kVec(float[] coords) {
		this();
		if (coords.length == 3) {
			x = coords[0];
			y = coords[1];
			z = coords[2];
		} else {
			System.out.println("warning - kVec constructed with bad coords array");
		}

	}
	
	/**
	 * Constructs a vector from two given points
	 * 
	 * @param p1
	 *            start point
	 * @param p2
	 *            end point
	 */
	public kVec(kVec p1, kVec p2) {
		x = p2.x - p1.x;
		y = p2.y - p1.y;
		z = p2.z - p1.z;
	}
	
	/**
	 * Constructs a vector from a comma-separated string
	 * 
	 * @param str
	 *            string
	 */
	public kVec(String str){
		try {
			str = str.replaceAll("\\(", "");
			str =str.replaceAll("\\)", "");
			str =str.replaceAll("\\[", "");
			str =str.replaceAll("\\]", "");
			String []splits = str.split(",");
			if (splits.length != 3){throw new EmptyStackException();}
			x = Float.valueOf(splits[0]).floatValue();
			y = Float.valueOf(splits[1]).floatValue();
			z = Float.valueOf(splits[2]).floatValue();
		} catch (Exception e) {
			System.out.println("!!!!! kVec(String str): "+e.toString());
		}
	}
	
	/**
	 * Creates a clone of a given vector
	 * @param t
	 * 		the vector to clone
	 * @return
	 */
	public static kVec clone(kVec t) {
		return new kVec(t.x, t.y, t.z);
	}
	
	/**
	 * Creates a random vector within a given min and max value for each coordinate 
	 * @param minV
	 * 		the minimum value for each coordinate
	 * @param maxV
	 * 		the minimum value for each coordinate
	 * @return
	 * 		a new random vector
	 */
	public static kVec randomVec( float minV, float maxV ){
		return new kVec( kUtil.randFloat(minV, maxV), kUtil.randFloat(minV, maxV), kUtil.randFloat(minV, maxV) );
	}

	/**
	 * Creates a random vector within a given range 
	 * @param range
	 * 		the range wihtin which to create the new vector
	 * @return
	 * 		a new random vector
	 */
	public static kVec randomVec( kRange range ){
		return new kVec( kUtil.randFloat(range.minX(), range.maxX()), kUtil.randFloat(range.minY(), range.maxY()), kUtil.randFloat(range.minZ(), range.maxZ()) );
	}
	
	/*
	 * METHODS
	 * 
	 * 
	 * 
	 */
	
	/**
	 * Determines if this vector is identical to a given vector
	 * within the tolerence defined by kElement.tolerence (default of 0.0001f)
	 * @param t
	 * @return
	 */
	public boolean isIdentical(kVec t){
		return isIdentical(t,kElement.tolerence);
	}
	/**
	 * Determines if this vector is identical to a given vector, within a given tolerence
	 * @param t
	 * 		the vector to compare against
	 * @param thresh
	 * 		the threshold factor
	 * @return
	 * 		boolean
	 */
	public boolean isIdentical(kVec t, float thresh){
		float dx = Math.abs(t.x-x);
		float dy = Math.abs(t.y-y);
		float dz = Math.abs(t.z-z);		
		//System.out.println("isIdentical["+dx+","+dy+","+dz+"]");
		return ((dx<=thresh)&&(dy<=thresh)&&(dz<=thresh));
	}
	
	/**
	 * Determines if this vector is coincident to a given vector (points the exact same direction as)
	 * within the tolerence defined by kElement.tolerence (default of 0.0001f) 
	 * @param t
	 * 		the vector to compare against
	 * @return
	 * 		boolean
	 */
	public boolean isCoincident(kVec t){
		kVec va = normalize(t);
		kVec vb = normalize(this);
		return va.isIdentical(vb);
	}
	

	/**
	 * Normalizes this vector (sets its length to 1 unit)
	 */
	public void normalize() {
		float norm;
		norm = (float) (1.0 / Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z));
		this.x *= norm;
		this.y *= norm;
		this.z *= norm;
	}
	/**
	 * Normalizes a given vector (sets its length to 1 unit)
	 * @param vec
	 * 		the vector to normalize
	 * @return
	 * 		the normalized vector
	 */
	public static kVec normalize(kVec vec) { kVec v = clone(vec); v.normalize(); return v; }	
	
	/**
	 * Inverts this vector so that it points in the opposite direction
	 */
	public void invert(){
		x = -x;
		y = -y;
		z = -z;
	}
	/**
	 * Inverts a given vector so that it points in the opposite direction
	 * @param vec
	 * 		the vector to invert
	 * @return
	 * 		the inverted vector
	 */
	public static kVec invert(kVec vec){kVec v = clone(vec);v.invert();return v;}	
	
	/**
	 * Resizes this vector such that its length is equal to a given distance
	 * @param size
	 * 		the desired length for this vector
	 */
	public void resize(float size){
		this.normalize();
		this.scale(size);
	}
	/**
	 * Resizes a given vector such that its length is equal to a given distance
	 * @param vec
	 * 		the vector to resize
	 * @param size
	 * 		the desired length
	 * @return
	 * 		the resized vector
	 */
	public static kVec resize(kVec vec, float size){kVec v = clone(vec);v.resize(size);return v;}
	
	/**
	 * Subtracts a given vector FROM this vector
	 * @param t
	 * 		the vector to subtract
	 */
	public void minus(kVec t){
		this.x -= t.x;
		this.y -= t.y;
		this.z -= t.z;
	}
	/**
	 * Subtracts vec1 FROM vec2 (vec1-vec2)
	 * @param vec1
	 * 		the vector to subtract from
	 * @param vec2
	 * 		the vector to subtract
	 * @return
	 * 		the resulting vector
	 */
	public static kVec minus(kVec vec1, kVec vec2){kVec v = clone(vec1);v.minus(vec2);return v;}
	
	/**
	 * Adds a given vector to this vector
	 * @param t
	 * 		the vector to add to this one
	 */
	public void plus(kVec t){
		this.x += t.x;
		this.y += t.y;
		this.z += t.z;
	}
	/**
	 * Adds two vectors and returns the result
	 * @param vec1
	 * 		the first vector to add
	 * @param vec2
	 * 		the second vector to add
	 * @return
	 * 		the resulting vector
	 */
	public static kVec plus(kVec vec1, kVec vec2){kVec v = clone(vec1);v.plus(vec2);return v;}
	
	/**
	 * Rounds each coordinate in this vector to the nearest integer number
	 */
	public void roundToInt(){
		x = (int) x;
		y = (int) y;
		z = (int) z;
	}
	/**
	 * Rounds each coordinate in a given vector to the nearest integer number
	 * @param vec
	 * 		the vector to round
	 * @return
	 * 		the rounded vector
	 */
	public static kVec roundToInt(kVec vec){kVec v = clone(vec);v.roundToInt();return v;}

	
	/**
	 * Returns the squared length of this vector.
	 * 
	 * @return the squared length of this vector
	 */
	public float lengthSquared() {
		return (this.x * this.x + this.y * this.y + this.z * this.z);
	}

	/**
	 * Returns the length of this vector.
	 * 
	 * @return the length of this vector
	 */
	public float length() {
		return (float) Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
	}
	
	/**
	 * Returns a new vector defined to be the vector cross product of vectors v1 and v2.
	 * 
	 * @param v1
	 *            the first vector
	 * @param v2
	 *            the second vector
	 */
	public static kVec cross(kVec v1, kVec v2) {
		float x = v1.y * v2.z - v1.z * v2.y;
		float y = v2.x * v1.z - v2.z * v1.x;
		float z = v1.x * v2.y - v1.y * v2.x;
		return new kVec(x,y,z);
	}

	/**
	 * Computes the dot product of this vector and vector v1.
	 * 
	 * @param v1
	 *            the other vector
	 * @return the dot product of this vector and v1
	 */
	public float dot(kVec v1) {
		return (this.x * v1.x + this.y * v1.y + this.z * v1.z);
	}
	
	/**
	 * Linearly interpolates between vectors v1 and v2 and returns the result point = (1-alpha)*v1 + alpha*v2.
	 * 
	 * @param v1
	 *            the first point
	 * @param v2
	 *            the second point
	 * @param alpha
	 *            the alpha interpolation parameter
	 */
	public static kVec interpolate(kVec v1, kVec v2, float alpha) {
		float x = (1 - alpha) * v1.x + alpha * v2.x;
		float y = (1 - alpha) * v1.y + alpha * v2.y;
		float z = (1 - alpha) * v1.z + alpha * v2.z;
		return new kVec(x, y, z);
	}	
	
	/**
	 * Returns the angle in radians between this vector and the vector parameter; the return value is constrained to the range [0,PI].
	 * 
	 * @param v1
	 *            the other vector
	 * @return the angle in radians in the range [0,PI]
	 */
	public float angle(kVec v1) {
		double vDot = this.dot(v1) / (this.length() * v1.length());
		if (vDot < -1.0) vDot = -1.0;
		if (vDot > 1.0) vDot = 1.0;
		return ((float) (Math.acos(vDot)));
	}

	/**
	 * Limits this vector to a given size.
	 * @param size
	 * 	the size to limit it to
	 */
	public void limit(float size){
        if (this.length() > size) {
            this.normalize();
            this.scale(size);
        }
    } 
	
	
	/**
	 * Returns the square of the distance between this tuple and tuple t1.
	 * 
	 * @param t1
	 *            the other tuple
	 * @return the square of the distance
	 */
	public float distanceSquared(kVec t1) {
		float dx, dy, dz;

		dx = this.x - t1.x;
		dy = this.y - t1.y;
		dz = this.z - t1.z;
		return (dx * dx + dy * dy + dz * dz);
	}

    /**
     * Scales this vector in place about 0,0,0.
     * @param s
     * 		the scale factor
     */
    public void scale(float s){
		this.x *= s;
		this.y *= s;
		this.z *= s;
    }
	public static kVec scale(kVec vec, float s){kVec v = clone(vec);v.scale(s);return v;}    
    /**
     * Scales this vector in place along the x-axis
     * @param s
     * 		the scale factor
     */
    public void scaleX(float s){this.x *= s;}
    /**
     * Scales this vector in place along the y-axis
     * @param s
     * 		the scale factor
     */
    public void scaleY(float s){this.y *= s;}
    /**
     * Scales this vector in place along the z-axis
     * @param s
     * 		the scale factor
     */
    public void scaleZ(float s){this.z *= s;}
	
    /**
     * Translates this point by a given vector
     * @param vec
     * 		the translation vector
     */
    public void translate(kVec vec){
    	this.plus(vec);
    }
    /**
     * Creates a new point by translating a given point by a given vector.  
     * Doesn't effect the given point.
     * @param point
     * 		the point to translate
     * @param vec
     * 		the translation vector
     * @return
     * 		a new point in the translated location
     */
    public static kVec translate(kVec point, kVec vec){kVec p = clone(point);p.translate(vec);return p;}
	
	/**
	 * Translates this vector in place along given vector, by a given distance
	 * @param vec
	 * 		the translation vector
	 * @param dist
	 * 		the distance to translate
	 */
	public void translate(kVec vec, float dist) {
		kVec v = kVec.resize(vec, dist);
		translate(v);
	}	
	/**
     * Creates a new point by translating a given point by along given vector, by a given distance.  
     * Doesn't effect the given point. 
	 * @param point
	 * 		the point to translate
	 * @param vec
	 * 		the translation vector
	 * @param dist
	 * 		the distance to translate
	 * @return
	 * 		a new point in the translated location
	 */
	public static kVec translate(kVec point, kVec vec, float dist){kVec p = clone(point);p.translate(vec, dist);return p;}	
    
	/**
	 * Finds the centroid of a given array of points
	 * @param pArr
	 * 		the array of points to find the center of
	 * @return
	 * 		a new kVec at the center of the given points
	 */
	public static kVec centroid(kVec[] pArr) {
		kVec result = new kVec();
		for (int index = 0; index < pArr.length; index++) {
			result.x += pArr[index].x;
			result.y += pArr[index].y;
			result.z += pArr[index].z;
		}
		result.x /= pArr.length;
		result.y /= pArr.length;
		result.z /= pArr.length;
		return result;
	}
    
	/**
	 * Finds the distance between this point and another point
	 * @param pt
	 * 		the other point
	 * @return
	 * 		a new positive float number indicating the distance
	 */
	public float distance(kVec pt) {
		float dx, dy, dz;
		dx = this.x - pt.x;
		dy = this.y - pt.y;
		dz = this.z - pt.z;
		return (float) Math.sqrt(dx * dx + dy * dy + dz * dz);
	}
	/**
	 * Finds the distance between two points
	 * @param p1
	 * 		the first point
	 * @param p2
	 * 		the second point
	 * @return
	 * 		a new positive float number indicating the distance
	 */
	public static float distance(kVec p1, kVec p2) {return p1.distance(p2);}
	
	/**
	 * Calculates the normalized bisector of vector a and vector b
	 * @param a
	 * 		vector a
	 * @param b
	 * 		vector b
	 * @return
	 * 		the bisector vector with a length of 1.0f
	 */
	public static kVec bisector( kVec a, kVec b ){
		kVec an = kVec.normalize(a);
		kVec bn = kVec.normalize(b);
		kVec ret = kVec.centroid( new kVec[] {an,bn} );
		ret.normalize();
		
		return ret;
	}
	
	/**
	 * Projects this vector onto the destination vector
	 * @param vec
	 * 		the vector to project to
	 */
	public void projectTo(kVec destVec)
	{	
	    float t = this.dot(destVec)/destVec.dot(destVec);
	    this.x = destVec.x; this.y = destVec.y; this.z = destVec.z;
	    this.scale(t);
	}
	
	/**
	 * Returns the vector which results from the projection of the source vector onto the destination vector
	 * 
	 * @param srcVec
	 * @param destVec
	 * @return
	 */
	public static kVec project(kVec srcVec, kVec destVec) {
		return resize(destVec, projectedLength(srcVec, destVec));
	}

	/**
	 * Returns the length of the vector which results from the projection of the source vector onto the destination vector
	 * 
	 * @param srcVec
	 * @param destVec
	 * @return
	 */
	public static float projectedLength(kVec srcVec, kVec destVec) {
		return srcVec.dot(normalize(destVec));
	}	
	
	
	
	/**
	 * Rotates this point about a cardnial axis
	 * @param axis
	 * 		the axis to rotate about; 0=x, 1=y, 2=z
	 * @param radians
	 * 		the amount to rotate in RADIANS
	 * @param centerOfRotation
	 * 		the center of rotation
	 */
	public void rotateAboutCardnialAxis( int axis , float radians , kVec centerOfRotation){
		translate(kVec.invert(centerOfRotation));
		float xx = x;
		float yy = y;
		float zz = z;
		if (axis == 0){
			yy = (float) ( this.y*Math.cos(radians) - this.z*Math.sin(radians) );
			zz = (float) ( this.y*Math.sin(radians) + this.z*Math.cos(radians) );
			x = xx;
			y = yy;
			z = zz;
		}
		if (axis == 1){
			zz = (float) ( this.z*Math.cos(radians) - this.x*Math.sin(radians) );
			xx = (float) ( this.z*Math.sin(radians) + this.x*Math.cos(radians) );
			x = xx;
			y = yy;
			z = zz;
		}
		if (axis == 2){
			xx = (float) ( this.x*Math.cos(radians) - this.y*Math.sin(radians) );
			yy = (float) ( this.x*Math.sin(radians) + this.y*Math.cos(radians) );
			x = xx;
			y = yy;
			z = zz;
		}
		translate(centerOfRotation);
	}	
	/**
	 * Rotates this point about a cardnial axis
	 * @param axis
	 * 		the axis to rotate about; 0=x, 1=y, 2=z
	 * @param degrees
	 * 		the amount to rotate in DEGREES
	 * @param centerOfRotation
	 */
	public void rotateAboutCardnialAxisDegrees( int axis , float degrees, kVec centerOfRotation ){ rotateAboutCardnialAxis(axis,(float)(degrees*Math.PI/180.0),centerOfRotation );}
	/**
	 * Rotates this point about a cardnial axis
	 * @param axis
	 * 		the axis to rotate about; 0=x, 1=y, 2=z
	 * @param radians
	 * 		the amount to rotate in RADIANS
	 */
	public void rotateAboutCardnialAxis( int axis , float radians ){rotateAboutCardnialAxis( axis , radians, new kVec(0,0,0) );}
	/**
	 * Rotates this point about a cardnial axis
	 * @param axis
	 * 		the axis to rotate about; 0=x, 1=y, 2=z
	 * @param radians
	 * 		the amount to rotate in DEGREES
	 */
	public void rotateAboutCardnialAxisDegrees( int axis , float degrees ){ rotateAboutCardnialAxis(axis,(float)(degrees*Math.PI/180.0) );}
	
	/**
	 * Flips two coordinates of a given point, and returns the result
	 * @param pt
	 * 		the point to flip around
	 * @param axisFlip
	 * 		the axis to flip around about, negative numbers reverse each other.
	 * 		0 (default) => no flipping, same point will result
	 * 		1  => x=y, y=z, z=x
	 * 		-1 => x=z, y=x, z=y
	 * 		2  => x=x, y=z, z=y
	 * 		-2 => x=x, y=z, z=y
	 * @return
	 * 		a new point with the axis flipped around
	 */
	public static kVec flipAxis(kVec pt, int axisFlip){
		kVec ret = new kVec();
		switch (axisFlip){
		case 1:
			// y-z plane primary
			ret.x = pt.y;
			ret.y = pt.z;
			ret.z = pt.x;
			break;
		case -1:
			// y-z plane primary inverse
			ret.x = pt.z;
			ret.y = pt.x;
			ret.z = pt.y;
			break;			
		case 2:
			// x-z plane primary
			ret.x = pt.x;
			ret.y = pt.z;
			ret.z = pt.y;
			break;
		case -2:
			// y-z plane primary inverse
			ret.x = pt.x;
			ret.y = pt.z;
			ret.z = pt.y;
			break;	
		default:
			// no flip
			ret.x = pt.x;
			ret.y = pt.y;
			ret.z = pt.z;
			break;
			
		}
		return ret;
	}	
	



	
	/*
	 * UTIL
	 * 
	 * 
	 * 
	 */
	
	public String toString(){
	   return "("+x+","+y+","+z+")";
	}

}
